package com.gx.security.asymmetric;

import org.apache.commons.codec.binary.Base64;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import javax.crypto.Cipher;
import javax.crypto.KeyAgreement;
import javax.crypto.SecretKey;
import javax.crypto.interfaces.DHPrivateKey;
import javax.crypto.interfaces.DHPublicKey;
import javax.crypto.spec.DHParameterSpec;
import java.io.IOException;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * Created by gx on 2017/3/29.
 */

/**
 * Diffie- Hellman算法(D-H算法)，密钥一致协议。
 * 是由公开密钥密码体制的奠基人Diffie和Hellman所提出的一种思想。
 * 简单的说就是允许两名用 户在公开媒体上交换信息以生成"一致"的、可以共享的密钥。
 * 换句话说，就是由甲方产出一对密钥（公钥、私钥），乙方依照甲方公钥产生乙方密钥对（公钥、私 钥）。
 * 以此为基线，作为数据传输保密基础，同时双方使用同一种对称加密算法构建本地密钥（SecretKey）对数据加密。
 * 这样，在互通了本地密钥 （SecretKey）算法后，甲乙双方公开自己的公钥，使用对方的公钥和刚才产生的私钥加密数据，
 * 同时可以使用对方的公钥和自己的私钥对数据解密。
 * 不单 单是甲乙双方两方，可以扩展为多方共享数据通讯，这样就完成了网络交互数据的安全通讯！该算法源于中国的同余定理——中国馀数定理。
 流程分析：
 1.甲方构建密钥对儿，将公钥公布给乙方，将私钥保留；双方约定数据加密算法；乙方通过甲方公钥构建密钥对儿，将公钥公布给甲方，将私钥保留。
 2.甲方使用私钥、乙方公钥、约定数据加密算法构建本地密钥，然后通过本地密钥加密数据，发送给乙方加密后的数据；
 乙方使用私钥、甲方公钥、约定数据加密算法构建本地密钥，然后通过本地密钥对数据解密。
 3.乙方使用私钥、甲方公钥、约定数据加密算法构建本地密钥，然后通过本地密钥加密数据，发送给甲方加密后的数据；
   甲方使用私钥、乙方公钥、约定数据加密算法构建本地密钥，然后通过本地密钥对数据解密。
 */

public class DHTest {
    private static String src = "imooc security DH";
    private static String password = "gaoxing";
    private static final String PUBLIC_KEY = "SenderDHPublicKey";
    private static final String PRIVATE_KEY = "SenderDHPrivateKey";
    private static final int KEY_SIZE = 1024;

    public static final String ALGORITHM = "DH";






    // 慕课网 test
    public static void jdkDHencrypt() {
        try {
            //初始化发送方秘钥
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DH");
            //设置长度
            keyPairGenerator.initialize(512);
            //得到key对
            KeyPair senderKeyPair = keyPairGenerator.generateKeyPair();
            byte[] senderPublicKeyPair = senderKeyPair.getPublic().getEncoded();// 发送给接受方（网路、文件）
            System.out.println("我是服务端 我发送给接收方 公钥----------------》");
//            System.out.println( new String(senderPublicKeyPair));


            System.out.println("客户端收到√");

            //初始化接收方秘钥
            KeyFactory receiverfactory = KeyFactory.getInstance("DH");
//            一个标准
            X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(senderPublicKeyPair);
//            从工厂中的到公钥
            PublicKey receiverPublic = receiverfactory.generatePublic(x509EncodedKeySpec);
//            得道DH的parameter
            DHParameterSpec dhParameterSpec = ((DHPublicKey) receiverPublic).getParams();
            //设置 KeyPair 算法
            KeyPairGenerator receiverKeyPairGenerator = KeyPairGenerator.getInstance("DH");
            //初始化  通过公钥  初始化 KeyPairGenerator
            receiverKeyPairGenerator.initialize(dhParameterSpec);
//            得道keyPair
            KeyPair receiverKeyPair = receiverKeyPairGenerator.genKeyPair();
            PrivateKey receiverPrivateKey = receiverKeyPair.getPrivate();
            byte[] receiverPublicKey = receiverKeyPair.getPublic().getEncoded();

//            new String("我是客户端  逆向生成publicKey 我发送 给服务端公钥----------------》","UTF-8");
            System.out.println("我是客户端  逆向生成publicKey 我发送 给服务端公钥----------------》");




            //客户端密钥构建
            KeyAgreement receiverAgreement = KeyAgreement.getInstance("DH");
            //初始化
            receiverAgreement.init(receiverPrivateKey);
            receiverAgreement.doPhase(receiverPublic, true);
            //用发送方的公钥 定位本地
            System.out.println("我是客户端 生成 加密算法Des");
            System.out.println("我是客户端  receiverDESSecreKey");
            SecretKey receiverDESSecreKey = receiverAgreement.generateSecret("DES");


            System.out.println("我是服务端   接受到客户端 的公钥----------------》" );
            //发送发接受
            KeyFactory senderKeyFacroty = KeyFactory.getInstance("DH");
            X509EncodedKeySpec senderx509EncodedKeySpec = new X509EncodedKeySpec(receiverPublicKey);
            //从客户端得到公钥
            PublicKey senderPublicKey = senderKeyFacroty.generatePublic(senderx509EncodedKeySpec);
            //服务端生成KeyAgreement
            KeyAgreement senderAgreement = KeyAgreement.getInstance("DH");
//            初始化 KeyAgreement  和我 服务端原来的 keypair中得道 私钥
            System.out.println("我是服务端 和原来服务端的 keypair中得到私钥");
            senderAgreement.init(senderKeyPair.getPrivate());
            senderAgreement.doPhase(senderPublicKey, true);




            //生成发送方本地密钥
            System.out.println("我是服务端 生成 加密算法Des");
            System.out.println("我是服务端  senderDESSecreKey");
            SecretKey senderDESSecreKey = senderAgreement.generateSecret("DES");
            //比较key是不是一样
            System.out.println("我是服务端 查看 key是不是一样");
            if (Objects.equals(receiverDESSecreKey, senderDESSecreKey)) {
                System.out.println("秘钥对比一样");
            }else {
                System.out.println("秘钥对比不一样");
            }

            //加密
            Cipher cipher   = Cipher.getInstance("DES");
            cipher.init(Cipher.ENCRYPT_MODE,senderDESSecreKey);
            byte [] result = cipher.doFinal(src.getBytes());
            System.out.println("JDK DH 加密 ："+ Base64.encodeBase64String(result));

            //解密
            cipher.init(Cipher.DECRYPT_MODE,receiverDESSecreKey);
            result = cipher.doFinal(result);
            System.out.println("JDK DH 解密 ："+ new String(result));


        } catch (Exception e) {
            e.printStackTrace();
        }
    }



    /**
     * 取得私钥
     *
     * @param keyMap
     * @return
     * @throws Exception
     */
    public static String getPrivateKey(Map keyMap) throws Exception {
        Key key = (Key) keyMap.get(PRIVATE_KEY);
        return new BASE64Encoder().encode(key.getEncoded());
    }

    /**
     * 取得公钥
     *
     * @param keyMap
     * @return
     * @throws Exception
     */
    public static String getPublicKey(Map keyMap) throws Exception {
        Key key = (Key) keyMap.get(PUBLIC_KEY);
        return new BASE64Encoder().encode(key.getEncoded());
    }


    /**
     * 加密
     *
     * @param data       待加密数据
     * @param publicKey  甲方公钥
     * @param privateKey 乙方私钥
     * @return
     * @throws Exception
     */
    public static String encrypt(String data, String publicKey, String privateKey) throws Exception {
        // 生成本地密钥
        SecretKey secretKey = getSecretKey(publicKey, privateKey);
        // 数据加密
        Cipher cipher = Cipher.getInstance(secretKey.getAlgorithm());
        cipher.init(Cipher.ENCRYPT_MODE, secretKey);
//        return new BASE64Encoder().encode(cipher.doFinal(data.getBytes("UTF-8")));
        return Base64.encodeBase64String(cipher.doFinal(data.getBytes("UTF-8")));

    }

    /**
     * 解密
     *
     * @param data       待解密数据
     * @param publicKey  乙方公钥
     * @param privateKey 乙方私钥
     * @return
     * @throws Exception
     */
    public static String decrypt(String data, String publicKey, String privateKey) throws Exception {
// 生成本地密钥
        SecretKey secretKey = getSecretKey(publicKey, privateKey);
// 数据解密
        Cipher cipher = Cipher.getInstance(secretKey.getAlgorithm());
        cipher.init(Cipher.DECRYPT_MODE, secretKey);
        byte[] decode_data = new BASE64Decoder().decodeBuffer(data);
        return new String(cipher.doFinal(decode_data));
    }


    /**
     * 构建秘钥
     * @param publicKey
     * @param privateKey
     * @return
     */
    private static SecretKey getSecretKey(String publicKey, String privateKey) throws NoSuchAlgorithmException, InvalidKeySpecException, IOException, InvalidKeyException {

//        byte[] pubKeyBytes = new BASE64Decoder().decodeBuffer(publicKey);
        byte[] pubKeyBytes = Base64.decodeBase64(publicKey);
        //发送发接受  初始化公钥
        KeyFactory keyFactory = KeyFactory.getInstance("DH");
        X509EncodedKeySpec senderx509EncodedKeySpec = new X509EncodedKeySpec(pubKeyBytes);
        //从客户端得到公钥
        PublicKey senderPublicKey = keyFactory.generatePublic(senderx509EncodedKeySpec);

        // 初始化私钥
//        byte[] priKeyBytes = new BASE64Decoder().decodeBuffer(privateKey);
        byte[] priKeyBytes = Base64.decodeBase64(privateKey);
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(priKeyBytes);
        Key priKey = keyFactory.generatePrivate(pkcs8KeySpec);
        KeyAgreement senderAgreement = KeyAgreement.getInstance("DH");
//            初始化 KeyAgreement  和我 服务端原来的 keypair中得道 私钥
        System.out.println("我是服务端 和原来服务端的 keypair中得到私钥");
        senderAgreement.init(priKey);
        senderAgreement.doPhase(priKey, true);
        return senderAgreement.generateSecret("DES");
    }


    /**
     * 初始化甲方密钥
     *
     * @return
     * @throws Exception
     */
    public static Map initKey() throws Exception {
        //初始化发送方秘钥
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DH");
        //设置长度
        keyPairGenerator.initialize(512);
        //得到key对
        KeyPair senderKeyPair = keyPairGenerator.generateKeyPair();
        // 甲方公钥
        DHPublicKey publicKey = (DHPublicKey) senderKeyPair.getPublic();
        // 甲方私钥
        DHPrivateKey privateKey = (DHPrivateKey) senderKeyPair.getPrivate();
        Map keyMap = new HashMap(2);
        keyMap.put(PUBLIC_KEY, publicKey);
        keyMap.put(PRIVATE_KEY, privateKey);
        System.out.println("我是甲方  我现在生成 key对   我发送给乙方 公钥----------------》");
        return keyMap;
    }


//    // 初始化 乙方秘钥对 接受 甲方公钥逆向生成
    public static Map<String,String> initKey(String key) throws Exception {
        byte [] senderPublicKeyPair = Base64.decodeBase64(key);
//        byte[] senderPublicKeyPair = new BASE64Decoder().decodeBuffer(key);

        //初始化接收方秘钥
        KeyFactory receiverfactory = KeyFactory.getInstance("DH");
//            一个标准 得到公钥输出标准
        X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(senderPublicKeyPair);
//            从工厂中的到公钥
        PublicKey receiverPublic = receiverfactory.generatePublic(x509EncodedKeySpec);


        //            得道DH的parameter
        DHParameterSpec dhParameterSpec = ((DHPublicKey) receiverPublic).getParams();
        //设置 KeyPair 算法
        KeyPairGenerator receiverKeyPairGenerator = KeyPairGenerator.getInstance("DH");
        //初始化  通过公钥  初始化 KeyPairGenerator
        receiverKeyPairGenerator.initialize(dhParameterSpec);
//            得道keyPair
        KeyPair receiverKeyPair = receiverKeyPairGenerator.genKeyPair();

//        乙方公钥
        DHPublicKey receiverPublicKey = (DHPublicKey) receiverKeyPair.getPublic();
//        乙方私钥
        DHPrivateKey receiverPrivateKey = (DHPrivateKey) receiverKeyPair.getPrivate();
        Map keyMap = new HashMap(2);
        keyMap.put(PUBLIC_KEY, receiverPublicKey);
        keyMap.put(PRIVATE_KEY, receiverPrivateKey);
        return keyMap;
    }






    public static void main(String[] args) throws Exception {
        System.out.println("程序开始甲方生成 秘钥对");
        Map sendermap = initKey();
        String senderPublic = getPublicKey(sendermap);
        String senderPrivate = getPrivateKey(sendermap);
        System.out.println("甲方生成公钥为："+senderPublic);
        System.out.println();
        System.out.println("甲方生成私钥为："+senderPrivate);
        System.out.println();
        System.out.println("现在甲方将公钥发送给乙方自己保留私钥");


        System.out.println("客户端收到√");
        System.out.println("乙方秘钥对 接受 甲方公钥逆向生成  密钥对");
//        Map receiverKeyMap = initKey(senderPublic);
        Map receiverKeyMap = initKey(senderPublic);
        String receiverPublicKey = getPublicKey(receiverKeyMap);
        String receiverPrivateKey = getPrivateKey(receiverKeyMap);
        System.out.println("乙方生成公钥为："+receiverPublicKey);
        System.out.println();
        System.out.println("乙方生成私钥为："+receiverPrivateKey);
        System.out.println();
        String input = "DH算法测试";
        System.out.println("原文: " + input);
//        String aCode = DHdemo.encrypt(input, receiverPublicKey, receiverPrivateKey);
//        System.out.println("由甲方公钥,乙方私钥构建密文: " + aCode);
// 由甲方公钥，乙方私钥构建密文
        String aCode = DHdemo.encrypt(input, senderPublic, receiverPrivateKey);
        System.out.println("由甲方公钥,乙方私钥构建密文: " + aCode);
// 由乙方公钥，甲方私钥解密
        String aDecode = DHdemo.decrypt(aCode, receiverPublicKey, senderPrivate);
        System.out.println("由乙方公钥,甲方私钥解密: " + aDecode);
        System.out.println(" ===============反过来加密解密================== ");
// 由乙方公钥，甲方私钥构建密文
        String bCode = DHdemo.encrypt(input, receiverPublicKey, senderPrivate);
        System.out.println("由乙方公钥,甲方私钥构建密文: " + bCode);
// 由甲方公钥，乙方私钥解密
        String bDecode = DHdemo.decrypt(bCode, senderPublic, receiverPrivateKey);
        System.out.println("由甲方公钥,乙方私钥解密: " + bDecode);



/**
 * 代码证实，甲乙双方在获得对方公钥后可以对发送给对方的数据加密，同时也能对接收到的数据解密，达到了数据安全通信的目的！
 */




    }


//    Publci  PublicKey


}
